/****************************************************************************
**
** Copyright (C) 2014 Kurt Pattyn <pattyn.kurt@gmail.com>.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtWebSockets module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "cabinetclient.h"
#include "linksettings.h"
#include "lfiledialog.h"
#include <QtCore/QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QFileDialog>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QTimer>
#include <QProcess>
#include <QCoreApplication>
#include <QMessageBox>
#include <QStandardPaths>
#include <QStack>
#include <QDesktopWidget>
#include "mainwindow.h"
#include "qprocesstask.h"
#include <QApplication>
#include <QDialogButtonBox>
#include <QJsonArray>
#include <QListView>
#include <QTreeView>
#include <QPushButton>

QT_USE_NAMESPACE

#define w() qWarning()
#define d() qDebug()
#define i() qInfo()
#define f() qFatal()

//! [constructor]
CabinetClient::CabinetClient(QObject *parent) :
    QObject(parent),
    m_WS_server("test", QWebSocketServer::NonSecureMode, this)
{
    m_pWS_remote = new QWebSocket("local", QWebSocketProtocol::VersionLatest, this);

    connect(m_pWS_remote, &QWebSocket::connected, [this](){
        i() << QString("remote connected @%1:%2")
            .arg(m_pWS_remote->peerAddress().toString())
            .arg(m_pWS_remote->peerPort());
        emit sig_statusChange("Server Connected");

        m_kicked = false;

        QJsonObject obj;
        obj["ddj"] = "yes";
        obj["type"] = "login";
        obj["user_id"] = m_client_id;

        auto msg = QJsonDocument(obj).toJson(QJsonDocument::Compact);
        m_pWS_remote->sendTextMessage(msg);
        i() << "Send msg:" << msg.toStdString().c_str();
    });
    connect(m_pWS_remote, &QWebSocket::disconnected, [this](){
        emit sig_statusChange("Server Disconnected");

        i() << "remote disconnected";
        if (!m_kicked){
            auto ms = 5000;
            i() << QString("Reconnect to remote server in %1ms").arg(ms);
            QTimer::singleShot(ms, this, [this](){
                if (m_kicked) {
                    QJsonObject _obj;
                    _obj["type"] = "cc_status";
                    _obj["status"] = "kicked";
                    QString res = QJsonDocument(_obj).toJson(QJsonDocument::Compact).toStdString().c_str();
                    emit sig_local_send_default(res);

                    return;
                }

                connectToServer();
            });
        }
    });
    connect(m_pWS_remote, &QWebSocket::textMessageReceived, this, &CabinetClient::onRemoteMessageReceived);

    connect(&m_qnam, &QNetworkAccessManager::finished, this, [](QNetworkReply* r){
        r->close();
        r->deleteLater();
    });

    slt_ReloadConfig();

    this->startTimer(100);
}

CabinetClient::~CabinetClient()
{
    emit sig_destruct();
}

bool CabinetClient::init()
{
    if (m_WS_server.listen(QHostAddress::LocalHost, m_local_port)) {
        i() << "Local websocket listening on localhost:" << m_local_port;

        emit sig_statusChange("Wait for ClientID");

        connect(&m_WS_server, &QWebSocketServer::newConnection, this, &CabinetClient::onLocalNewConnection);
        connect(&m_WS_server, &QWebSocketServer::acceptError, this, [](QAbstractSocket::SocketError socketError){
            w() << "Local accept error" << socketError;
        });
        connect(&m_WS_server, &QWebSocketServer::serverError, this, [](QWebSocketProtocol::CloseCode closeCode){
            w() << "Local server error" << closeCode;
        });

        return true;
    }
    else {
        qWarning() << m_WS_server.errorString();
        emit sig_statusChange("Listen Error");
        return false;
    }
}

void CabinetClient::slt_ReloadConfig()
{
    auto ls = LinkSettings::Inst();

    m_debug = ls->getDebugEnabled();
    m_local_port = ls->getLocalWebsocketPort();
    m_server_port = ls->getRemoteWebsocketPort();
    m_DownloadDefaultDirOn = ls->getDownloadDefaultDirOn();
    m_DownloadDefaultDir = ls->getDownloadDefaultDir();
    m_TaskExecuteName = ls->getTaskExecuteName();
}

//! [constructor]

//! [onConnected]
void CabinetClient::onRemoteConnected()
{
    auto ws = qobject_cast<QWebSocket*>(sender()); Q_ASSERT(ws);
    i() << QString("remote connected @%1:%2").arg(ws->peerAddress().toString()).arg(ws->peerPort());
    emit sig_statusChange("Server Connected");

    QJsonObject obj;
    obj["ddj"] = "yes";
    obj["type"] = "login";
    obj["user_id"] = m_client_id;

    auto msg = QJsonDocument(obj).toJson();
    m_pWS_remote->sendTextMessage(msg);

    i() << "Send msg:" << msg.toStdString().c_str();
}
//! [onConnected]

//! [onTextMessageReceived]
void CabinetClient::onRemoteMessageReceived(QString message)
{
    if (m_debug){
        d() << "[Message remote]" << message.toLocal8Bit().toStdString().c_str();
    }

    message = message.trimmed();
    if (message.isEmpty()) {
        w() << "Empty message from server";
        return;
    }
    else {
        handleRemoteMessage(message);
    }

    //m_webSocket.close();
}

void CabinetClient::handleLocalMessage(QWebSocket* ws, const QString &msg)
{
    if(m_debug) d() << "[local]: " << msg.toLocal8Bit().toStdString().c_str();

    if (msg.trimmed().isEmpty()) {
        w() << "Empty message from local websocket";
        return;
    }

    QJsonParseError parseErr;
    auto doc = QJsonDocument::fromJson(msg.toLocal8Bit(), &parseErr);
    if (doc.isNull()) {
        w() << parseErr.errorString() << msg << doc.toJson();
        return;
    }

    if (!doc.isObject()) {
        w() << "invalid json object" << msg;
        return;
    }

    auto obj = doc.object();
    if (!obj.contains("type")) {
        d() << "message without type" << msg;
        return;
    }

    auto msg_type = obj["type"].toString();
    if (msg_type == "init") {
        if (!obj.contains("client_id") || obj["client_id"].toString().trimmed().isEmpty()) {
            w() << "invalid init message without client_id" << msg;
            return;
        }

        if (!obj.contains("host") || obj["host"].toString().trimmed().isEmpty()) {
            w() << "invalid init message without host" << msg;
            return;
        }

        auto c_id = obj["client_id"].toString().trimmed();
        auto host = obj["host"].toString().trimmed();

        if (m_pWS_remote->isValid() && m_client_id == c_id && m_server_host == host) {
            w() << "client_id"<<c_id<<"@"<<host<<"has login, duplicate client, do nothing";

            obj["result"] = "ok";
            obj["msg"] = QString("duplicate client @ %1:%2")
                .arg(ws->peerAddress().toString())
                .arg(ws->peerPort());

            QJsonObject _obj;
            _obj["type"] = "cc_status";
            _obj["status"] = "logged";
            QString res = QJsonDocument(_obj).toJson(QJsonDocument::Compact).toStdString().c_str();
            emit sig_local_send_default(res);
        }
        else {
            obj["result"] = "ok";

            // assign the server info
            m_client_id = c_id;
            m_server_host = host;

            m_pWS_remote->close();
            connectToServer();
        }
        emit sig_local_broadcast(QJsonDocument(obj).toJson(QJsonDocument::Compact).toStdString().c_str());
    }
    else if (msg_type == "get_local_path") {
        {
            QMutexLocker ml(&m_dlg_mtx);
            if (m_dlg) {
                //dlg->reject();
                m_dlg->deleteLater();
                m_dlg = nullptr;

                emit sig_file_dialog_hide();
            }
        }
        if (obj.contains("cancel")) {
            return;
        }

        QString path = "";        
        QByteArray title_raw = QByteArray::fromPercentEncoding(obj.value("title").toString().toLocal8Bit());
        QString title = title_raw.toStdString().c_str();
        if (title.isEmpty()) {
            title = "路径选择";
        }
        auto type = obj.value("sub_type").toString();
        auto multi = obj.value("multiple").toBool();


        emit sig_file_dialog_show();
        if (type == "dir" && multi) {
            auto dlg = new LFileDialog(qobject_cast<QWidget *>(this->parent()));
            dlg->setWindowTitle(title);
            {
                QMutexLocker ml(&m_dlg_mtx);
                m_dlg = dlg;
            }

            connect(dlg, &LFileDialog::accepted, [dlg, obj, ws, this](){
                auto _obj = obj;
                QJsonArray arr;
                for (auto fi: dlg->selectedItems()) {
                    arr.append(fi);
                }
                auto dir = dlg->selectedDir();
                if (!dir.isEmpty()) {
                    _obj["local_path"] = dir;
                    _obj["local_files"] = arr;
                }
                else {
                    _obj["local_path"] = arr[0];
                    _obj["local_files"] = QJsonArray();
                }

                _obj["selected"] = "1";
                QString res = QJsonDocument(_obj).toJson(QJsonDocument::Compact).toStdString().c_str();
                if (m_debug) d() << "on accepted" << res << _obj;

                emit sig_local_send(ws, res);
                emit sig_file_dialog_hide();
            });
            connect(dlg, &LFileDialog::rejected, [obj, ws, this](){
                auto _obj = obj;
                _obj["local_path"] = "";
                _obj["selected"] = "0";
                QString res = QJsonDocument(_obj).toJson(QJsonDocument::Compact).toStdString().c_str();
                if (m_debug) d() << "on rejected" << res << _obj;

                emit sig_local_send(ws, res);
                emit sig_file_dialog_hide();
            });

            dlg->open();
        }
        else {
            auto dlg = new QFileDialog(qobject_cast<QWidget *>(this->parent()));
            {
                QMutexLocker ml(&m_dlg_mtx);
                m_dlg = dlg;
            }

            connect(dlg, &QFileDialog::accepted, [dlg, obj, ws, this](){
                auto _obj = obj;
                QJsonArray arr;
                for (auto fi: dlg->selectedFiles()) {
                    arr.append(QFileInfo(fi).fileName());
                }
                _obj["local_path"] = QFileInfo(dlg->selectedFiles().at(0)).absoluteDir().absolutePath();
                _obj["local_files"] = arr;
                _obj["selected"] = "1";
                QString res = QJsonDocument(_obj).toJson(QJsonDocument::Compact).toStdString().c_str();
                if (m_debug) d() << "on accepted" << res << _obj;

                emit sig_local_send(ws, res);

                {
                    QMutexLocker ml(&m_dlg_mtx);
                    m_dlg->deleteLater();
                    m_dlg = nullptr;
                }

                emit sig_file_dialog_hide();
            });
            connect(dlg, &QFileDialog::rejected, [obj, ws, this](){
                auto _obj = obj;
                _obj["local_path"] = "";
                _obj["selected"] = "0";
                QString res = QJsonDocument(_obj).toJson(QJsonDocument::Compact).toStdString().c_str();
                if (m_debug) d() << "on rejected" << res << _obj;

                emit sig_local_send(ws, res);
                {
                    QMutexLocker ml(&m_dlg_mtx);
                    m_dlg->deleteLater();
                    m_dlg = nullptr;
                }

                emit sig_file_dialog_hide();
            });

            QDesktopWidget *desk=QApplication::desktop();
            int wd = desk->width(), ht = desk->height();
            dlg->move((wd - dlg->width())/2, (ht - dlg->height())/2);
            dlg->setWindowTitle(title);
            dlg->setModal(false);

            if (type == "dir") {
                dlg->setOptions(QFileDialog::DontResolveSymlinks | QFileDialog::ShowDirsOnly);
                dlg->setFileMode(QFileDialog::Directory);
            }
            else if (type == "file"){
                dlg->setOptions(QFileDialog::DontResolveSymlinks);
                dlg->setFileMode(multi?QFileDialog::ExistingFiles:QFileDialog::Directory);
            }
            else{
                dlg->setOptions(QFileDialog::DontResolveSymlinks | QFileDialog::DontUseNativeDialog);
                dlg->setFileMode(QFileDialog::AnyFile);
            }
            dlg->open();
        }
    }
    else {
        d() << "unknown message type" << msg;
    }
}

void CabinetClient::handleRemoteMessage(const QString &msg)
{
    QJsonParseError err;
    auto doc = QJsonDocument::fromJson(msg.toLocal8Bit(), &err);
    if (doc.isNull()) {
        w() << err.errorString();
        return;
    }

    if (!doc.isObject()) {
        if (m_debug) w() << "message is not valid json object";
        return;
    }

    auto obj = doc.object();
    if (!obj.contains("type")) {
        if (m_debug) w() << "message is not valid json object without type";
        return;
    }

    auto type = obj["type"];
    if (type == "login") {
        if (!obj.contains("client_id") || !obj.contains("room_id")) {
            w() << "message is not valid [login] message without client_id/room_id" << msg;
            return;
        }

        m_id = obj["client_id"].toString();
        m_room_id = obj["room_id"].toString();

        i()<<"client login success, as " << m_id << "@" << m_room_id;

        QJsonObject _obj;
        _obj["type"] = "cc_status";
        _obj["status"] = "logged";
        QString res = QJsonDocument(_obj).toJson(QJsonDocument::Compact).toStdString().c_str();
        emit sig_local_send_default(res);

        // for the first time
        fetchTask();
    }
    else if (type == "task_ready") {
        if (!obj.contains("task") || !obj["task"].isObject()) {
            w() << "message is not valid [task_ready] message without task object" << msg;
            return;
        }
        else {
            auto task_obj = obj["task"].toObject();
            if (m_debug) i() << "New Task"<<QJsonDocument(task_obj).toJson().toStdString().c_str();

            taskHandle(task_obj);
        }
    }
    else if (type == "kick") {
        QJsonObject _obj;
        _obj["type"] = "cc_status";
        _obj["status"] = "kicked";
        QString res = QJsonDocument(_obj).toJson(QJsonDocument::Compact).toStdString().c_str();
        emit sig_local_send_default(res);

        if (!m_kicked) {
            m_kicked = true;
            w() << "kick by remote" << obj["errmsg"].toString();
            emit sig_kicked_by_server();
        }
    }
    else if (type == "cancel_task") {
        QString task_id = obj.value("task_id").toString();
        if (task_id.isEmpty()) {
            w() << "message is not valid [cancel_task] message without task_id" << msg;
            return;
        }

        emit sig_cancel_task(task_id);
    }
    else {
        if (m_debug) w() << "unknown message type" << type;
    }
}

void CabinetClient::onLocalMessageReceived(const QString &msg)
{
    auto p_ws = qobject_cast<QWebSocket *>(sender()); Q_ASSERT(p_ws);

    if (msg.at(0) != '{') {
        w() << msg << " is not valid json.";
        return;
    }
    else if (m_debug){
        d() << msg;
    }

    int start_pos = 0;
    QStack<QChar> st;
    for(int i=0; i<msg.length(); i++) {
        auto c = msg.at(i);
        if (c == '[' || c == '{') {
            st.push(c);
        }
        else if (c == ']') {
            if (st.pop() != '[') {
                w() << msg << " is not valid json. pos: " << i;
                return;
            }
        }
        else if (c == '}') {
            if (st.pop() != '{') {
                w() << msg << " is not valid json. pos: " << i;
                return;
            }
        }
        else{
            if (st.isEmpty()) {
                w() << msg << " is not valid json. pos: " << i;
                return;
            }
        }

        if (st.isEmpty() && i > start_pos) {
            handleLocalMessage(p_ws, msg.mid(start_pos, i - start_pos + 1));
            start_pos = i + 1;
        }
    }
}

void CabinetClient::onLocalNewConnection()
{
    QWebSocket *pSocket = m_WS_server.nextPendingConnection();

    i() << "local connected" << pSocket->peerPort();

    connect(this, &CabinetClient::sig_local_broadcast, pSocket, &QWebSocket::sendTextMessage);
    connect(this, &CabinetClient::sig_local_send, [pSocket](QWebSocket* ws, QString msg){
        if (ws != pSocket) return;

        d() << "send to client" << msg;
        pSocket->sendTextMessage(msg);
    });
    connect(this, &CabinetClient::sig_local_send_default, pSocket, &QWebSocket::sendTextMessage);
    connect(pSocket, &QWebSocket::textMessageReceived, this, &CabinetClient::onLocalMessageReceived);
    connect(pSocket, &QWebSocket::disconnected, [pSocket](){
        d() << "local disconnected" << pSocket->peerPort();
        pSocket->deleteLater();
    });

}

void CabinetClient::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
    auto proc = qobject_cast<QProcessTask*>(sender()); Q_ASSERT (proc);

    i() << "ExitCode: " << exitCode << ", ExitStatus: " << exitStatus;
    auto is_succ = exitCode==0;

    finishTask(proc->task(), is_succ, is_succ?"Success":QString("Failure(%1)").arg(exitCode));
}

void CabinetClient::onFetchTaskError(QNetworkReply::NetworkError err)
{
    w() << "Fetch task error: " << err;
}

void CabinetClient::onFinishTaskError(QNetworkReply::NetworkError err)
{
    w() << "Finish task error: " << err;
}

void CabinetClient::taskHandle(QJsonObject obj)
{
    if (!obj.contains("type")) {
        w() << "invalid task object without type" << obj;
        return;
    }

    auto type = obj.value("type").toString().trimmed().toLower();
    if (type != "download" && type != "upload") {
        w() << "unknown task type:"<<type;
        return;
    }

    QProcessTask *proc = new QProcessTask(obj);
    {
        connect(this, &CabinetClient::sig_cancel_task, proc, &QProcessTask::SLT_CancelTask);

        connect(proc, &QProcessTask::SIG_heart, [this](QString task_id){
            Q_ASSERT(!task_id.isEmpty());

            auto url = QUrl(QString("http://%1/?a=ddj_task_heart&task_id=%2")
                .arg(m_server_host)
                .arg(task_id));
            m_qnam.get(QNetworkRequest(url));

            qDebug() << url;
        });
        connect(proc, &QProcessTask::taskFinished, [this, proc](int exitCode, QProcess::ExitStatus exitStatus){
            i() << "ExitCode: " << exitCode << ", ExitStatus: " << exitStatus;

            proc->deleteLater();
            auto is_succ = exitCode==0;

            finishTask(proc->task(), is_succ, is_succ?"Success":QString("Failure(%1)").arg(exitCode));
        });
        connect(proc, &QProcessTask::started, [this, proc](){
            i() << "Process started on " << proc->task();
            emit sig_task_start(proc->task());
        });
        connect(proc, &QProcess::errorOccurred, [this, proc](QProcess::ProcessError error){
            auto w_msg = QString("Process Error: %1").arg(error);
            w() << w_msg;
            finishTask(proc->task(), "no", w_msg);
        });
        connect(this, &CabinetClient::sig_kicked_by_server, proc, &QProcessTask::kill);
    }

    QString dst_path = obj.value("dst_path").toString();
    if (type == "download"){
        // if dst is empty
        if (dst_path.isEmpty()) {
            if (!m_DownloadDefaultDirOn) {
                bool ok;
                auto p_id = obj.value("parent_id").toString().trimmed().toInt(&ok);
                if (!ok) { w() << "invalid parent_id"; return; }

                dst_path = getLocalPath(p_id, obj);
                if (dst_path.isEmpty()) {
                    i() << "user cancel path selection.";
                    finishTask(obj, false, "user not select save path");

                    proc->deleteLater();
                    return;
                }
            }
            else {
                dst_path = m_DownloadDefaultDir;
            }
        }

        obj["dst_path"] = dst_path;
    }
    obj["host"] = m_server_host;

    QFileInfo fi(QDir(QDir(QCoreApplication::applicationDirPath()).filePath("task")).filePath(m_TaskExecuteName));
    if (!fi.exists()) {
        QString w_msg = QString("[%1] is missing").arg(fi.absoluteFilePath());
        w() << w_msg;

        QMessageBox::warning(qobject_cast<QWidget*>(this->parent()), "Fatal Error", w_msg.toLocal8Bit().data());
        exit(-1);
    }
    QString exe_path = fi.absoluteFilePath();
    QByteArray argv = QJsonDocument(obj).toJson(QJsonDocument::Compact);
    QStringList exe_args = { argv.toHex().toUpper().toStdString().c_str() };

    proc->start(exe_path, exe_args);

    if (m_debug) d() << exe_path << obj << exe_args;
}

QString CabinetClient::getLocalPath(const int id, const QJsonObject& task) const
{
    thread_local int last_id = -1;
    thread_local QString path;
    if (last_id == id && !path.isEmpty()) {
        return path;
    }

    auto src = getTaskSrc(task);
    auto dst = getTaskDst(task);
    QString title = QString("[%1]=>[%2]").arg(src).arg(dst);

    auto lst = QStandardPaths::standardLocations(QStandardPaths::DownloadLocation);
    static auto def_dir = lst.size()>0?*lst.begin():"C:/";
    last_id = id;
    path = QFileDialog::getExistingDirectory(
        qobject_cast<QWidget *>(this->parent()),
        title,
        def_dir,
        QFileDialog::DontResolveSymlinks | QFileDialog::ShowDirsOnly);
    def_dir = path;
    return path;
}

void CabinetClient::fetchTask(int cnt)
{
    if (cnt-- <= 0) return;

    Q_ASSERT(!m_server_host.isEmpty() && !m_client_id.isEmpty());

    auto url = QUrl(QString("http://%1/?a=ddj_task_get_ready&client_id=%2&last_id=%3")
        .arg(m_server_host)
        .arg(m_client_id)
        .arg(m_last_task_id));

    auto r = m_qnam.get(QNetworkRequest(url));
    connect(r, &QNetworkReply::finished, this, [this, r, cnt](){
        auto code = r->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
        if (code == 200) {
            i() << "Get task fetch success.";
            return;
        }

        // need retry
        if (cnt > 0) {
            auto ms = 5000;
            i() << QString("[%1]Refetch task at %2ms").arg(cnt).arg(ms);

            QTimer::singleShot(ms, this, [this, cnt](){
                fetchTask(cnt);
            });
        }
    });
    connect(r, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onFetchTaskError(QNetworkReply::NetworkError)));

    i() << "Fetch task from" << url;
}

void CabinetClient::finishTask(QJsonObject task, bool result, QString msg)
{
    Q_ASSERT(!task.isEmpty() && task.contains("id") && !task["id"].toString().isEmpty());

    QJsonObject obj;
    obj["ddj"] = "yes";
    obj["type"] = "task";

    QJsonObject t;
    t["id"] = task["id"];
    t["type"] = "finish";
    t["result"] = result?"ok":"no";
    t["msg"] = msg;

    obj["task"] = t;

    QString data = QJsonDocument(obj).toJson(QJsonDocument::JsonFormat::Compact);
    m_pWS_remote->sendTextMessage(data);

    qInfo("success finish task:\n%s", data.toLocal8Bit().data());
    emit sig_task_done(task, result, msg);

    i() << "Finish task";
}

QString CabinetClient::getTaskSrc(const QJsonObject &task) const
{
    auto type = task.value("type").toString().trimmed();
    if (type == "download") {
        auto src = task.value("src_path").toString().trimmed();
        auto i = 0;
        i = src.indexOf("/", i);
        if (i > 0) {
            src = src.mid(i+1);
            i = src.indexOf("/", i);
            if (i>0) {
                src = src.mid(i+1);
            }
        }
        return src;
    }
    else {
        return "";
    }
}

QString CabinetClient::getTaskDst(const QJsonObject &task) const
{
    auto type = task.value("type").toString().trimmed();
    if (type == "download") {
        return task.value("dst_path").toString().trimmed();
    }
    else {
        return "";
    }
}

void CabinetClient::connectToServer()
{
    Q_ASSERT(!m_server_host.isEmpty() && m_server_port > 0);

    if (m_pWS_remote->state() != QAbstractSocket::UnconnectedState) {
        w() << "Already connected to server, ignore this request.";
        return;
    }

    m_url = QUrl(QStringLiteral("ws://%1:%2").arg(m_server_host).arg(m_server_port));
    m_pWS_remote->close();
    m_pWS_remote->open(m_url);

    i() << "try to log ["<<m_client_id <<"]@[" << m_url << "]...";
    emit sig_clientLogin(m_client_id, m_server_host);
}


//! [onTextMessageReceived]
